D:\git\skunkworks\herald-for-cpp\herald-tests\ranges-tests.cpp
Line | Count | Source |
1 | | // Copyright 2021 Herald Project Contributors |
2 | | // SPDX-License-Identifier: Apache-2.0 |
3 | | // |
4 | | |
5 | | #include "catch.hpp" |
6 | | |
7 | | #include <iterator> |
8 | | #include <iostream> |
9 | | |
10 | | #include "herald/herald.h" |
11 | | |
12 | 1 | TEST_CASE("ranges-iterator-proxy", "[ranges][iterator][proxy]") { |
13 | 1 | SECTION("ranges-iterator-proxy") { |
14 | 1 | herald::analysis::views::in_range<int> workingAge(18,65); |
15 | 1 | |
16 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages; |
17 | 1 | ages.push(10,12); |
18 | 1 | ages.push(20,14); |
19 | 1 | ages.push(30,19); |
20 | 1 | ages.push(40,45); |
21 | 1 | ages.push(50,66); |
22 | 1 | herald::analysis::views::iterator_proxy proxy(ages); |
23 | 1 | |
24 | 1 | REQUIRE(!proxy.ended()); |
25 | 1 | REQUIRE(*proxy == 12); |
26 | 1 | ++proxy; |
27 | 1 | REQUIRE(*proxy == 14); |
28 | 1 | ++proxy; |
29 | 1 | REQUIRE(*proxy == 19); |
30 | 1 | ++proxy; |
31 | 1 | REQUIRE(*proxy == 45); |
32 | 1 | ++proxy; |
33 | 1 | REQUIRE(*proxy == 66); |
34 | 1 | ++proxy; |
35 | 1 | REQUIRE(proxy.ended()); |
36 | 1 | } |
37 | 1 | } |
38 | | |
39 | 1 | TEST_CASE("ranges-filter-singlematch", "[ranges][singlematch]") { |
40 | 1 | SECTION("ranges-filter-singlematch") { |
41 | 1 | herald::analysis::views::in_range<int> firstCentenary(0,100); |
42 | 1 | |
43 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages; |
44 | 1 | ages.push(10,12); |
45 | 1 | |
46 | 1 | herald::analysis::views::filter<herald::analysis::views::in_range<int>> workingAgeFilter(firstCentenary); |
47 | 1 | |
48 | 1 | auto iter = workingAgeFilter(ages); |
49 | 1 | REQUIRE(!iter.ended()); |
50 | 1 | REQUIRE(*iter == 12); |
51 | 1 | ++iter; |
52 | 1 | REQUIRE(iter.ended()); |
53 | 1 | } |
54 | 1 | } |
55 | | |
56 | 1 | TEST_CASE("ranges-filter-allmatch", "[ranges][allmatch]") { |
57 | 1 | SECTION("ranges-filter-allmatch") { |
58 | 1 | herald::analysis::views::in_range<int> firstCentenary(0,100); |
59 | 1 | |
60 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages; |
61 | 1 | ages.push(10,12); |
62 | 1 | ages.push(20,14); |
63 | 1 | ages.push(30,19); |
64 | 1 | ages.push(40,45); |
65 | 1 | ages.push(50,66); |
66 | 1 | |
67 | 1 | herald::analysis::views::filter<herald::analysis::views::in_range<int>> workingAgeFilter(firstCentenary); |
68 | 1 | |
69 | 1 | auto iter = workingAgeFilter(ages); |
70 | 1 | REQUIRE(!iter.ended()); |
71 | 1 | REQUIRE(*iter == 12); |
72 | 1 | ++iter; |
73 | 1 | REQUIRE(!iter.ended()); |
74 | 1 | REQUIRE(*iter == 14); |
75 | 1 | ++iter; |
76 | 1 | REQUIRE(!iter.ended()); |
77 | 1 | REQUIRE(*iter == 19); |
78 | 1 | ++iter; |
79 | 1 | REQUIRE(!iter.ended()); |
80 | 1 | REQUIRE(*iter == 45); |
81 | 1 | ++iter; |
82 | 1 | REQUIRE(!iter.ended()); |
83 | 1 | REQUIRE(*iter == 66); |
84 | 1 | ++iter; |
85 | 1 | REQUIRE(iter.ended()); |
86 | 1 | } |
87 | 1 | } |
88 | | |
89 | 1 | TEST_CASE("ranges-filter-typed", "[ranges][typed]") { |
90 | 1 | SECTION("ranges-filter-typed") { |
91 | 1 | herald::analysis::views::in_range<int> workingAge(18,65); |
92 | 1 | |
93 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages; |
94 | 1 | ages.push(10,12); |
95 | 1 | ages.push(20,14); |
96 | 1 | ages.push(30,19); |
97 | 1 | ages.push(40,45); |
98 | 1 | ages.push(50,66); |
99 | 1 | |
100 | 1 | herald::analysis::views::filter<herald::analysis::views::in_range<int>> workingAgeFilter(workingAge); |
101 | 1 | |
102 | 1 | auto iter = workingAgeFilter(ages); |
103 | 1 | REQUIRE(!iter.ended()); |
104 | 1 | REQUIRE(*iter == 19); |
105 | 1 | ++iter; |
106 | 1 | REQUIRE(!iter.ended()); |
107 | 1 | REQUIRE(*iter == 45); |
108 | 1 | ++iter; |
109 | 1 | REQUIRE(iter.ended()); |
110 | 1 | } |
111 | 1 | } |
112 | | |
113 | 1 | TEST_CASE("ranges-filter-generic", "[ranges][generic]") { |
114 | 1 | SECTION("ranges-filter-generic") { |
115 | 1 | herald::analysis::views::in_range workingAge(18,65); |
116 | 1 | |
117 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages; |
118 | 1 | ages.push(10,12); |
119 | 1 | ages.push(20,14); |
120 | 1 | ages.push(30,19); |
121 | 1 | ages.push(40,45); |
122 | 1 | ages.push(50,66); |
123 | 1 | |
124 | 1 | auto workingAges = ages |
125 | 1 | | herald::analysis::views::filter(workingAge) |
126 | 1 | | herald::analysis::views::to_view(); |
127 | 1 | |
128 | 1 | auto iter = workingAges.begin(); |
129 | 1 | REQUIRE(iter != workingAges.end()); |
130 | 1 | REQUIRE(*iter == 19); |
131 | 1 | ++iter; |
132 | 1 | REQUIRE(*iter == 45); |
133 | 1 | |
134 | 1 | REQUIRE(workingAges.size() == 2); |
135 | 1 | REQUIRE(workingAges[0] == 19); |
136 | 1 | REQUIRE(workingAges[1] == 45); |
137 | 1 | } |
138 | 1 | } |
139 | | |
140 | 1 | TEST_CASE("ranges-filter-multi", "[ranges][filter][multi]") { |
141 | 1 | SECTION("ranges-filter-multi") { |
142 | 1 | herald::analysis::views::in_range workingAge(18,65); |
143 | 1 | herald::analysis::views::greater_than over21(21); |
144 | 1 | |
145 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<int>,5> ages; |
146 | 1 | ages.push(10,12); |
147 | 1 | ages.push(20,14); |
148 | 1 | ages.push(30,19); |
149 | 1 | ages.push(40,45); |
150 | 1 | ages.push(50,66); |
151 | 1 | |
152 | 1 | auto workingAges = ages |
153 | 1 | | herald::analysis::views::filter(workingAge) |
154 | 1 | | herald::analysis::views::filter(over21) |
155 | 1 | | herald::analysis::views::to_view(); |
156 | 1 | |
157 | 1 | auto iter = workingAges.begin(); |
158 | 1 | REQUIRE(iter != workingAges.end()); |
159 | 1 | REQUIRE(*iter == 45); |
160 | 1 | ++iter; |
161 | 1 | REQUIRE(iter == workingAges.end()); |
162 | 1 | |
163 | 1 | REQUIRE(workingAges.size() == 1); |
164 | 1 | REQUIRE(workingAges[0] == 45); |
165 | 1 | } |
166 | 1 | } |
167 | | |
168 | 1 | TEST_CASE("ranges-iterator-rssisamples", "[ranges][iterator][rssisamples][rssi]") { |
169 | 1 | SECTION("ranges-iterator-rssisamples") { |
170 | 1 | herald::analysis::views::in_range valid(-99,-10); |
171 | 1 | herald::analysis::views::less_than strong(-59); |
172 | 1 | |
173 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl; |
174 | 1 | sl.push(1234,-9); |
175 | 1 | sl.push(1244,-60); |
176 | 1 | sl.push(1265,-58); |
177 | 1 | sl.push(1282,-61); |
178 | 1 | sl.push(1294,-100); |
179 | 1 | |
180 | 1 | herald::analysis::views::iterator_proxy<herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5>> proxy(sl); |
181 | 1 | |
182 | 1 | REQUIRE(!proxy.ended()); |
183 | 1 | REQUIRE((*proxy).value == -9); |
184 | 1 | ++proxy; |
185 | 1 | REQUIRE((*proxy).value == -60); |
186 | 1 | ++proxy; |
187 | 1 | REQUIRE((*proxy).value == -58); |
188 | 1 | ++proxy; |
189 | 1 | REQUIRE((*proxy).value == -61); |
190 | 1 | ++proxy; |
191 | 1 | REQUIRE((*proxy).value == -100); |
192 | 1 | ++proxy; |
193 | 1 | REQUIRE(proxy.ended()); |
194 | 1 | } |
195 | 1 | } |
196 | | |
197 | 1 | TEST_CASE("ranges-listtoview", "[ranges][listtoview][rssi]") { |
198 | 1 | SECTION("ranges-listtoview") { |
199 | 1 | herald::analysis::views::in_range valid(-111,-1); |
200 | 1 | |
201 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl; |
202 | 1 | sl.push(1234,-9); |
203 | 1 | sl.push(1244,-60); |
204 | 1 | sl.push(1265,-58); |
205 | 1 | sl.push(1282,-61); |
206 | 1 | sl.push(1294,-100); |
207 | 1 | |
208 | 1 | auto values = sl |
209 | 1 | | herald::analysis::views::filter(valid) // Providing a filter as a list cannot convert directly to a view (what's the point? It's already a collection!) |
210 | 1 | | herald::analysis::views::to_view(); |
211 | 1 | |
212 | 1 | auto iter = values.begin(); |
213 | 1 | REQUIRE(iter != values.end()); |
214 | 1 | REQUIRE((*iter).value.intValue() == -9); |
215 | 1 | ++iter; |
216 | 1 | REQUIRE((*iter).value.intValue() == -60); |
217 | 1 | ++iter; |
218 | 1 | REQUIRE((*iter).value.intValue() == -58); |
219 | 1 | ++iter; |
220 | 1 | REQUIRE((*iter).value.intValue() == -61); |
221 | 1 | ++iter; |
222 | 1 | REQUIRE((*iter).value.intValue() == -100); |
223 | 1 | ++iter; |
224 | 1 | REQUIRE(iter == values.end()); |
225 | 1 | |
226 | 1 | REQUIRE(values.size() == 5); |
227 | 1 | auto val0 = values[0].value.intValue(); |
228 | 1 | auto val1 = values[1].value.intValue(); |
229 | 1 | auto val2 = values[2].value.intValue(); |
230 | 1 | auto val3 = values[3].value.intValue(); |
231 | 1 | auto val4 = values[4].value.intValue(); |
232 | 1 | REQUIRE(val0 == -9); |
233 | 1 | REQUIRE(val1 == -60); |
234 | 1 | REQUIRE(val2 == -58); |
235 | 1 | REQUIRE(val3 == -61); |
236 | 1 | REQUIRE(val4 == -100); |
237 | 1 | } |
238 | 1 | } |
239 | | |
240 | 1 | TEST_CASE("ranges-filter-multi-rssisamples", "[ranges][filter][multi][rssisamples][rssi]") { |
241 | 1 | SECTION("ranges-filter-multi-rssisamples") { |
242 | 1 | herald::analysis::views::in_range valid(-99,-10); |
243 | 1 | herald::analysis::views::less_than strong(-59); |
244 | 1 | |
245 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl; |
246 | 1 | sl.push(1234,-9); |
247 | 1 | sl.push(1244,-60); |
248 | 1 | sl.push(1265,-58); |
249 | 1 | sl.push(1282,-61); |
250 | 1 | sl.push(1294,-100); |
251 | 1 | |
252 | 1 | auto values = sl |
253 | 1 | | herald::analysis::views::filter(valid) |
254 | 1 | | herald::analysis::views::filter(strong) |
255 | 1 | | herald::analysis::views::to_view(); |
256 | 1 | |
257 | 1 | auto iter = values.begin(); |
258 | 1 | REQUIRE(iter != values.end()); |
259 | 1 | REQUIRE((*iter).value == -60); |
260 | 1 | ++iter; |
261 | 1 | REQUIRE((*iter).value == -61); |
262 | 1 | ++iter; |
263 | 1 | REQUIRE(iter == values.end()); |
264 | 1 | |
265 | 1 | REQUIRE(values.size() == 2); |
266 | 1 | auto val0 = values[0].value.intValue(); |
267 | 1 | auto val1 = values[1].value.intValue(); |
268 | 1 | REQUIRE(val0 == -60); |
269 | 1 | REQUIRE(val1 == -61); |
270 | 1 | } |
271 | 1 | } |
272 | | |
273 | 1 | TEST_CASE("ranges-filter-multi-rssitwosamples-nothingfiltered", "[ranges][filter][multi][rssitwosamples-nothingfiltered][rssi]") { |
274 | 1 | SECTION("ranges-filter-multi-rssitwosamples-nothingfiltered") { |
275 | 1 | herald::analysis::views::in_range valid(-99,-10); |
276 | 1 | herald::analysis::views::less_than strong(-59); |
277 | 1 | |
278 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl; |
279 | 1 | sl.push(1244,-60); |
280 | 1 | sl.push(1282,-61); |
281 | 1 | |
282 | 1 | auto values = sl |
283 | 1 | | herald::analysis::views::filter(valid) |
284 | 1 | | herald::analysis::views::filter(strong) |
285 | 1 | | herald::analysis::views::to_view(); |
286 | 1 | |
287 | 1 | auto iter = values.begin(); |
288 | 1 | REQUIRE(iter != values.end()); |
289 | 1 | REQUIRE((*iter).value.intValue() == -60); |
290 | 1 | ++iter; |
291 | 1 | REQUIRE((*iter).value.intValue() == -61); |
292 | 1 | ++iter; |
293 | 1 | REQUIRE(iter == values.end()); |
294 | 1 | |
295 | 1 | REQUIRE(values.size() == 2); |
296 | 1 | auto val0 = values[0].value.intValue(); |
297 | 1 | auto val1 = values[1].value.intValue(); |
298 | 1 | REQUIRE(val0 == -60); |
299 | 1 | REQUIRE(val1 == -61); |
300 | 1 | } |
301 | 1 | } |
302 | | |
303 | 1 | TEST_CASE("ranges-filter-multi-rssisinglesample-nothingfiltered", "[ranges][filter][multi][rssisinglesample-nothingfiltered][rssi]") { |
304 | 1 | SECTION("ranges-filter-multi-rssisinglesample-nothingfiltered") { |
305 | 1 | herald::analysis::views::in_range valid(-99,-10); |
306 | 1 | herald::analysis::views::less_than strong(-59); |
307 | 1 | |
308 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl; |
309 | 1 | sl.push(1244,-60); |
310 | 1 | |
311 | 1 | auto values = sl |
312 | 1 | | herald::analysis::views::filter(valid) |
313 | 1 | | herald::analysis::views::filter(strong) |
314 | 1 | | herald::analysis::views::to_view(); |
315 | 1 | |
316 | 1 | auto iter = values.begin(); |
317 | 1 | REQUIRE(iter != values.end()); |
318 | 1 | REQUIRE((*iter).value == -60); |
319 | 1 | ++iter; |
320 | 1 | REQUIRE(iter == values.end()); |
321 | 1 | |
322 | 1 | REQUIRE(values.size() == 1); |
323 | 1 | auto val0 = values[0].value.intValue(); |
324 | 1 | REQUIRE(val0 == -60); |
325 | 1 | } |
326 | 1 | } |
327 | | |
328 | 1 | TEST_CASE("ranges-filter-multi-rssinosample", "[ranges][filter][multi][rssinosample][rssi]") { |
329 | 1 | SECTION("ranges-filter-multi-rssinosample") { |
330 | 1 | herald::analysis::views::in_range valid(-99,-10); |
331 | 1 | herald::analysis::views::less_than strong(-59); |
332 | 1 | |
333 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,5> sl; |
334 | 1 | |
335 | 1 | auto values = sl |
336 | 1 | | herald::analysis::views::filter(valid) |
337 | 1 | | herald::analysis::views::filter(strong) |
338 | 1 | | herald::analysis::views::to_view(); |
339 | 1 | |
340 | 1 | auto iter = values.begin(); |
341 | 1 | REQUIRE(iter == values.end()); |
342 | 1 | |
343 | 1 | REQUIRE(values.size() == 0); |
344 | 1 | } |
345 | 1 | } |
346 | | |
347 | 1 | TEST_CASE("ranges-filter-multi-summarise", "[ranges][filter][multi][summarise][rssi]") { |
348 | 1 | SECTION("ranges-filter-multi-summarise") { |
349 | 1 | herald::analysis::views::in_range valid(-99,-10); |
350 | 1 | herald::analysis::views::less_than strong(-59); |
351 | 1 | |
352 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,20> sl; |
353 | 1 | sl.push(1234,-9); |
354 | 1 | sl.push(1244,-60); |
355 | 1 | sl.push(1265,-58); |
356 | 1 | sl.push(1282,-62); |
357 | 1 | sl.push(1282,-68); |
358 | 1 | sl.push(1282,-68); |
359 | 1 | sl.push(1294,-100); |
360 | 1 | |
361 | 1 | using namespace herald::analysis::aggregates; |
362 | 1 | auto values = sl |
363 | 1 | | herald::analysis::views::filter(valid) |
364 | 1 | | herald::analysis::views::filter(strong) |
365 | 1 | | herald::analysis::views::to_view(); |
366 | 1 | |
367 | 1 | auto summary = values |
368 | 1 | | summarise<Mean,Mode,Variance>(); |
369 | 1 | |
370 | 1 | auto mean = summary.get<Mean>(); |
371 | 1 | auto mode = summary.get<Mode>(); |
372 | 1 | auto var = summary.get<Variance>(); |
373 | 1 | |
374 | 1 | REQUIRE(mean == -64.5); // note conversion from RSSI -> int then aggregate -> float |
375 | 1 | REQUIRE(mode == -68); |
376 | 1 | REQUIRE(var == 17); // Happens to be exact, but you may need take in to account floating point inaccuracy in the tail |
377 | 1 | } |
378 | 1 | } |
379 | | |
380 | 1 | TEST_CASE("ranges-filter-multi-since-summarise", "[ranges][filter][multi][since][summarise][rssi]") { |
381 | 1 | SECTION("ranges-filter-multi-since-summarise") { |
382 | 1 | herald::analysis::views::in_range valid(-99,-10); |
383 | 1 | herald::analysis::views::less_than strong(-59); |
384 | 1 | herald::analysis::views::since afterPoint(herald::datatype::Date{1245}); |
385 | 1 | |
386 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,20> sl; |
387 | 1 | sl.push(1234,-9); |
388 | 1 | sl.push(1244,-60); |
389 | 1 | sl.push(1265,-58); |
390 | 1 | sl.push(1282,-62); |
391 | 1 | sl.push(1282,-68); |
392 | 1 | sl.push(1282,-68); |
393 | 1 | sl.push(1294,-100); |
394 | 1 | |
395 | 1 | using namespace herald::analysis::aggregates; |
396 | 1 | auto values = sl |
397 | 1 | | herald::analysis::views::filter(afterPoint) |
398 | 1 | | herald::analysis::views::filter(valid) |
399 | 1 | | herald::analysis::views::filter(strong) |
400 | 1 | | herald::analysis::views::to_view(); |
401 | 1 | |
402 | 1 | auto summary = values |
403 | 1 | | summarise<Mean,Mode,Variance>(); |
404 | 1 | |
405 | 1 | auto mean = summary.get<Mean>(); |
406 | 1 | auto mode = summary.get<Mode>(); |
407 | 1 | auto var = summary.get<Variance>(); |
408 | 1 | |
409 | 1 | REQUIRE(mean == -66); // note conversion from RSSI -> int then aggregate -> float |
410 | 1 | REQUIRE(mode == -68); |
411 | 1 | REQUIRE(var == 12); // Happens to be exact, but you may need take in to account floating point inaccuracy in the tail |
412 | 1 | } |
413 | 1 | } |
414 | | |
415 | 1 | TEST_CASE("ranges-distance-aggregate", "[ranges][distance][filter][multi][since][summarise][rssi][aggregate]") { |
416 | 1 | SECTION("ranges-distance-aggregate") { |
417 | 1 | herald::analysis::views::in_range valid(-99,-10); |
418 | 1 | herald::analysis::views::less_than strong(-59); |
419 | 1 | herald::analysis::views::since afterPoint(herald::datatype::Date{1245}); |
420 | 1 | |
421 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<herald::datatype::RSSI>,20> sl; |
422 | 1 | sl.push(1234,-9); |
423 | 1 | sl.push(1244,-60); |
424 | 1 | sl.push(1265,-58); |
425 | 1 | sl.push(1282,-62); |
426 | 1 | sl.push(1282,-68); |
427 | 1 | sl.push(1282,-68); |
428 | 1 | sl.push(1294,-100); |
429 | 1 | |
430 | 1 | using namespace herald::analysis::aggregates; |
431 | 1 | auto values = sl |
432 | 1 | | herald::analysis::views::filter(afterPoint) |
433 | 1 | | herald::analysis::views::filter(valid) |
434 | 1 | | herald::analysis::views::filter(strong) |
435 | 1 | | herald::analysis::views::to_view(); |
436 | 1 | |
437 | 1 | auto summary = values // is an r-value here |
438 | 1 | | summarise<Mean,Mode,Variance>(); |
439 | 1 | |
440 | 1 | auto mean = summary.get<Mean>(); |
441 | 1 | auto mode = summary.get<Mode>(); |
442 | 1 | auto var = summary.get<Variance>(); |
443 | 1 | auto sd = std::sqrt(var); |
444 | 1 | |
445 | 1 | // See second diagram at https://heraldprox.io/bluetooth/distance |
446 | 1 | // i.e. https://heraldprox.io/images/distance-rssi-regression.png |
447 | 1 | herald::analysis::algorithms::distance::FowlerBasic to_distance(-50, -24); |
448 | 1 | |
449 | 1 | auto distance = sl |
450 | 1 | | herald::analysis::views::filter(afterPoint) |
451 | 1 | | herald::analysis::views::filter(valid) |
452 | 1 | | herald::analysis::views::filter(strong) |
453 | 1 | | herald::analysis::views::filter( |
454 | 1 | herald::analysis::views::in_range( |
455 | 1 | mode - 2*sd, // NOTE: WE USE THE MODE FOR FILTER, BUT SD FOR BOUNDS - See website for the reasoning |
456 | 1 | mode + 2*sd |
457 | 1 | ) |
458 | 1 | ) |
459 | 1 | // | herald::analysis::views::to_view() // returns an l-value -> Have to wrap in a view here as we need an end iterator to evaluate in aggregate |
460 | 1 | | aggregate(to_distance); // type actually <herald::analysis::algorithms::distance::FowlerBasic> |
461 | 1 | |
462 | 1 | auto agg = distance.get<herald::analysis::algorithms::distance::FowlerBasic>(); |
463 | 1 | auto d = agg.reduce(); |
464 | 1 | REQUIRE((d > 5.623 && d < 5.624)); // double rounding |
465 | 1 | |
466 | 1 | // Now do the same for an in-line temporary aggregate... |
467 | 1 | |
468 | 1 | auto distance2 = sl |
469 | 1 | | herald::analysis::views::filter(afterPoint) |
470 | 1 | | herald::analysis::views::filter(valid) |
471 | 1 | | herald::analysis::views::filter(strong) |
472 | 1 | | herald::analysis::views::filter( |
473 | 1 | herald::analysis::views::in_range( |
474 | 1 | mode - 2*sd, // NOTE: WE USE THE MODE FOR FILTER, BUT SD FOR BOUNDS - See website for the reasoning |
475 | 1 | mode + 2*sd |
476 | 1 | ) |
477 | 1 | ) |
478 | 1 | // | herald::analysis::views::to_view() // returns an l-value -> Now have a helper in aggregate so we don't need to use to_view |
479 | 1 | | aggregate(herald::analysis::algorithms::distance::FowlerBasic(-50, -24)); // TRYING WITH A TEMPORARY - CHECKING IT DOES STD::MOVE CORRECTLY |
480 | 1 | |
481 | 1 | auto agg2 = distance2.get<herald::analysis::algorithms::distance::FowlerBasic>(); |
482 | 1 | auto d2 = agg2.reduce(); |
483 | 1 | REQUIRE((d2 > 5.623 && d2 < 5.624)); // double rounding |
484 | 1 | } |
485 | 1 | } |
486 | | |
487 | | // Risk aggregation example implementation |
488 | 1 | TEST_CASE("ranges-risk-aggregate", "[ranges][risk][aggregate][no-filter]") { |
489 | 1 | SECTION("ranges-risk-aggregate") { |
490 | 1 | // First we simulate a list of actual distance samples over time, using a vector of pairs |
491 | 1 | std::vector<std::pair<herald::datatype::Date,double>> sourceDistances; |
492 | 1 | sourceDistances.emplace_back(1235,5.5); |
493 | 1 | sourceDistances.emplace_back(1240,4.7); |
494 | 1 | sourceDistances.emplace_back(1245,3.9); |
495 | 1 | sourceDistances.emplace_back(1250,3.2); |
496 | 1 | sourceDistances.emplace_back(1255,2.2); |
497 | 1 | sourceDistances.emplace_back(1260,1.9); |
498 | 1 | sourceDistances.emplace_back(1265,1.0); |
499 | 1 | sourceDistances.emplace_back(1270,1.3); |
500 | 1 | sourceDistances.emplace_back(1275,2.0); |
501 | 1 | sourceDistances.emplace_back(1280,2.2); |
502 | 1 | |
503 | 1 | // The below would be in your aggregate handling code... |
504 | 1 | herald::analysis::sampling::SampleList<herald::analysis::sampling::Sample<double>, 2> distanceList; |
505 | 1 | |
506 | 1 | // For n distances we maintain n-1 distance-risks in a list, and continuously add to it |
507 | 1 | // (i.e. we don't recalculate risk over all previous time - too much data) |
508 | 1 | // Instead we keep a distance-time number for this known 'contact' which lasts up to 15 minutes. |
509 | 1 | // (i.e. when the mac address changes in Bluetooth) |
510 | 1 | // We would then store that single risk-time number against that single contact ID - much less data! |
511 | 1 | double timeScale = 1.0; // default is 1 second |
512 | 1 | double distanceScale = 1.0; // default is 1 metre, not scaled |
513 | 1 | double minimumDistanceClamp = 1.0; // As per Oxford Risk Model, anything < 1m ... |
514 | 1 | double minimumRiskScoreAtClamp = 1.0; // ...equals a risk of 1.0, ... |
515 | 1 | // double logScale = 1.0; // ... and falls logarithmically after that |
516 | 1 | // NOTE: The above values are pick for testing and may not be epidemiologically accurate! |
517 | 1 | herald::analysis::algorithms::risk::RiskAggregationBasic riskScorer(timeScale,distanceScale,minimumDistanceClamp,minimumRiskScoreAtClamp); |
518 | 1 | |
519 | 1 | using namespace herald::analysis::aggregates; |
520 | 1 | |
521 | 1 | // this does nothing other than initialise our riskSlice reference |
522 | 1 | auto riskSlice = distanceList |
523 | 1 | // no filters or any other iterator-proxy style class here... |
524 | 1 | //| herald::analysis::views::to_view() // Now we can pipe lists straight in to aggregates and summarise calls |
525 | 1 | | aggregate(riskScorer); // moves riskScorer in to aggregate instance |
526 | 1 | |
527 | 1 | // Now generate a sequence of Risk Scores over time |
528 | 1 | double interScore = 0.0; |
529 | 1 | double firstNonZeroInterScore = 0.0; |
530 | 10 | for (auto&[when,distance] : sourceDistances) { |
531 | 10 | // A new distance has been calculated! |
532 | 10 | distanceList.push(when,distance); |
533 | 10 | // Let's see if we have a new risk score! |
534 | 10 | riskSlice = distanceList |
535 | 10 | // no filters or any other iterator-proxy style class here... |
536 | 10 | | riskSlice; |
537 | 10 | // Add to our exposure risk for THIS contact |
538 | 10 | // Note: We're NOT resetting over time, as the riskScorer will hold our total risk exposure from us. |
539 | 10 | // We could instead extract this slice, store it in a counter, and reset the risk Scorer if |
540 | 10 | // we needed to alter the value somehow or add the risk slices themselves to a new list. |
541 | 10 | // Instead, we only do this for each contact in total (i.e. up to 15 minutes per riskScorer). |
542 | 10 | auto& agg = riskSlice.get<herald::analysis::algorithms::risk::RiskAggregationBasic>(); |
543 | 10 | interScore = agg.reduce(); |
544 | 10 | if (firstNonZeroInterScore == 0.0 && interScore > 06 ) { |
545 | 1 | firstNonZeroInterScore = interScore; |
546 | 1 | } |
547 | 10 | INFO("RiskAggregationBasic inter score: " << interScore << " address of agg: " << &agg); |
548 | 10 | } |
549 | 1 | |
550 | 1 | // Now we have the total for our 'whole contact duration', not scaled for how far in the past it is |
551 | 1 | auto& agg = riskSlice.get<herald::analysis::algorithms::risk::RiskAggregationBasic>(); |
552 | 1 | double riskScore = agg.reduce(); |
553 | 1 | INFO("RiskAggregationBasic final score: " << riskScore << " address of agg: " << &agg); |
554 | 1 | REQUIRE(interScore > 0.0); // final inter score should be non zero |
555 | 1 | REQUIRE(riskScore > 0.0); // final score should be non zero |
556 | 1 | REQUIRE(riskScore > firstNonZeroInterScore); // should be additive over time too |
557 | 1 | } |
558 | 1 | } |
559 | | |
560 | | // TODO Given a list of risk-distance numbers, and the approximate final time of that contact, calculate |
561 | | // a risk score when the risk of infection drops off linearly over 14 days. (like COVID-19) |
562 | | // (Ideally we'd have a more robust epidemiological model, but this will suffice for example purposes) |